# -*- coding: utf-8 -*-

import importlib
import sys
import time
from collections import deque
from datetime import datetime, timedelta
from functools import partial
import datetime as dt
from typing import List
# 其他
import numpy as np
import pandas as pd
import pyqtgraph as pg

from bqgraph.qt import QtCore,pyqtSignal,pyqtSlot,Qt

from bqgraph.qt import QFont
from bqgraph.qt import QApplication
from bqgraph.qt import QtWidgets, QtGui


from bqgraph.cross_hair import CrossHair as Crosshair
from bqgraph.const_graph import *
from bqgraph.key_wraper import KeyWrapper

importlib.reload(sys)
# # sys.setdefaultencoding('utf8')
# # 字符串转换
# # ---------------------------------------------------------------------------------------
# try:
#     _fromUtf8 = QtCore.QString.fromUtf8
# except AttributeError:
#     def _fromUtf8(s):
#         return s

# 每日最多Tick数，每秒2根，白天4小时+夜盘3+2.5小时
MAX_TICKS = 2 * 60 * 60 * (4 + 5.5)
#周期英中字典
period_en_zh_dict = {
    "TICK": "分时",
    "M1": "1分钟",
    "M3": "3分钟",
    "M5": "5分钟",
    "M10": "10分钟",
    "M15": "15分钟",
    "M30": "30分钟",
    "H1": "1小时",
    "DAY": "日",
    "WEEK": "周",
    "MONTH": "月",
    "YEAR": "年"
}
period_zh_en_dict = {v: k for k, v in period_en_zh_dict.items()}

########################################################################
# 时间序列，横坐标支持
########################################################################
class StringAxisX(pg.AxisItem):
    """时间序列横坐标支持"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, xdict, *args, **kwargs):
        pg.AxisItem.__init__(self, *args, **kwargs)
        self.minVal = 0
        self.maxVal = 100
        self.xdict = xdict
        self.x_values = np.asarray(list(xdict.keys()))  # np.asarray(xdict.keys())
        self.x_strings = np.asarray(list(self.xdict.values()))
        self.setPen(color=(255, 255, 255, 255), width=0.8)
        self.setStyle(tickFont=QFont("Roman times", 10, QFont.Bold), autoExpandTextSpace=True)
        # self.scale = 1

    # 更新坐标映射表
    # ----------------------------------------------------------------------
    def update_xdict(self, xdict):
        self.xdict.update(xdict)
        self.x_values = np.asarray(list(xdict.keys()))  # np.asarray(xdict.keys())
        self.x_strings = np.asarray(list(self.xdict.values()))
        # print('scale=, spacing={}, self.scale={}'.format('super()._tickSpacing', self.scale))

    # 将原始横坐标转换为时间字符串,第一个坐标包含日期
    # ----------------------------------------------------------------------
    def tickStrings(self, values, scale, spacing):
        strings = []

        for v in values:
            vs = int(v * scale)
            if vs in self.x_values:
                # print(strings, values, scale, spacing, self.scale)
                vstr = self.x_strings[np.abs(self.x_values - vs).argmin()]
                vstr = vstr.strftime('%Y-%m-%d %H:%M:%S')
            else:
                vstr = ""
            strings.append(vstr)
        # print(strings, 'scale={}, spacing={}, self.scale={}'.format( scale, spacing, self.scale))
        return strings

class DatetimeAxisX(pg.AxisItem):

    def __init__(self, datas, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.data = datas  # 传入k线数据
        self.setPen(pg.mkPen(QtGui.QColor(255, 0, 0), width=1))  # 设置时间轴边框颜色
        self.tickFont = QtGui.QFont("Arial", 6)  # 时间轴刻度字体


    def tickStrings(self, values: List[int], scale: float, spacing: int):
        """
        Convert original index to datetime string.
        values自动传入x坐标
        """
        # 行索引和时间组合字典，行索引和x轴对应
        datetime_index_map = dict(zip(self.data.index, self.data.datetime))
        strings = []
        for ix in values:
            dt = datetime_index_map.get(ix, None)  # x轴对应的时间
            if not dt:
                s = ""
            else:
                dt = datetime.fromtimestamp(dt / 1e9)  # 转换成datetime格式
                if dt.hour:  # 日内周期
                    s = dt.strftime("%m-%d %H:%M")
                else:
                    s = dt.strftime("%Y-%m-%d")
            strings.append(s)
        return strings


########################################################################
# K线图形对象
########################################################################
class CandlestickItem(pg.GraphicsObject):
    """K线图形对象"""


    def __init__(self, data: list):  # 初始化
        pg.GraphicsObject.__init__(self)
        # 只重画部分图形，大大提高界面更新速度
        self.rect = None
        self.picture = None
        self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemUsesExtendedStyleOption)

        self.offset = 0
        self.low = 0
        self.high = 1
        self.picture = QtGui.QPicture()
        self.pictures = []
        self.generatePicture(data)  # 刷新K线


    def generatePicture(self, data=None, redraw=False):  # 画K线
        """重新生成图形对象"""
        if redraw:  # 重画或者只更新最后一个K线
            self.pictures = []
        elif self.pictures:
            self.pictures.pop()
        w = 0.4  # k线一半的宽度

        if len(data) > 0:
            self.low = np.min(data['low'])
            self.high = np.max(data['high'])
        else:
            self.low, self.high = (0, 1)

        npic = len(self.pictures)
        for (i, open_i, high_i, low_i, close_i, volume_i) in data:
            if i >= npic:
                picture = QtGui.QPicture()
                p = QtGui.QPainter(picture)
                # 画蜡烛图,下跌绿色（实心）, 上涨红色（空心）
                if close_i < open_i:  # 阴线情况
                    p.setPen(pg.mkPen(QtGui.QColor(0, 255, 0), width=2))  # 设置画笔颜色，宽度
                    p.setBrush(pg.mkBrush(QtGui.QColor(0, 255, 0)))
                    p.drawLine(QtCore.QPointF(i, low_i), QtCore.QPointF(i, high_i))  # 画上下影线
                    p.drawRect(QtCore.QRectF(i - w, open_i, w * 2, close_i - open_i))  # 画矩形，实心K线

                elif close_i > open_i:  # 阳线情况
                    p.setPen(pg.mkPen(QtGui.QColor(255, 0, 0), width=2))  # red
                    p.setBrush(pg.mkBrush(QtGui.QColor(255, 0, 0)))  # red
                    if (high_i != close_i):  # 如果最高点不等于收盘价，画上影线
                        p.drawLine(QtCore.QPointF(i, high_i), QtCore.QPointF(i, close_i))
                    if (low_i != open_i):  # 如果最低点不等于开盘价，画下影线
                        p.drawLine(QtCore.QPointF(i, open_i), QtCore.QPointF(i, low_i))

                    # p.drawRect(QtCore.QRectF(i - w, open, w * 2, close - open)) # 如果画实心阳线，只需画个实心矩形即可
                    # 画空心阳线的时候，需要画四条线

                    p.drawLine(QtCore.QPointF(i - w, open_i), QtCore.QPointF(i - w, close_i))  # 画单根K线的左边线
                    p.drawLine(QtCore.QPointF(i + w, open_i), QtCore.QPointF(i + w, close_i))  # 画单根K线的右边线
                    p.drawLine(QtCore.QPointF(i - w, close_i), QtCore.QPointF(i + w, close_i))  # 画单根K线的上边线
                    p.drawLine(QtCore.QPointF(i - w, open_i), QtCore.QPointF(i + w, open_i))  # 画单根K线的下边线

                else:  # 平盘情况
                    p.setPen(pg.mkPen(QtGui.QColor(0, 0, 255), width=2))  # 十字线时设为蓝色
                    p.setBrush(pg.mkBrush(QtGui.QColor(0, 0, 255)))

                    p.drawLine(QtCore.QPointF(i, high_i), QtCore.QPointF(i, low_i))  # 画上下影线
                    p.drawLine(QtCore.QPointF(i - w, close_i), QtCore.QPointF(i + w, close_i))  # 画一条横线

                p.end()
                self.pictures.append(picture)


    def update(self):  # 手动重画
        if not self.scene() is None:
            self.scene().update()


    def paint(self, painter, opt, w):  # 自动重画
        rect = opt.exposedRect
        xmin, xmax = (max(0, int(rect.left())), min(int(len(self.pictures)), int(rect.right())))
        if not self.rect == (rect.left(), rect.right()) or self.picture is None:
            self.rect = (rect.left(), rect.right())
            self.picture = self.createPic(xmin, xmax)
            self.picture.play(painter)
        elif not self.picture is None:
            self.picture.play(painter)


    def createPic(self, xmin, xmax):  # 缓存图片
        picture = QtGui.QPicture()
        p = QtGui.QPainter(picture)
        [pic.play(p) for pic in self.pictures[xmin:xmax]]
        p.end()
        return picture


    def boundingRect(self):  # 定义边界
        return QtCore.QRectF(0, self.low, len(self.pictures), (self.high - self.low))


########################################################################
# 成交量图形对象
########################################################################
class VolumeItem(pg.GraphicsObject):
    """K线图形对象"""


    def __init__(self, data: list):  # 初始化
        """初始化"""
        pg.GraphicsObject.__init__(self)
        self.rect = None
        self.picture = None
        self.setFlag(QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemUsesExtendedStyleOption)  # 只重画部分图形，大大提高界面更新速度

        self.offset = 0
        self.low = 0
        self.high = 1
        self.picture = QtGui.QPicture()
        self.pictures = []
        self.generatePicture(data)  # 刷新柱线


    def generatePicture(self, data=None, redraw=False):  # 画柱线
        """重新生成图形对象"""
        # 重画或者只更新最后一个K线
        if redraw:
            self.pictures = []
        elif self.pictures:
            self.pictures.pop()
        w = 0.4  # k线一半的宽度
        self.low = 0
        if len(data) > 0:
            self.high = np.max(data['volume'])
        else:
            self.high = 1
        npic = len(self.pictures)
        for (i, open_i, high_i, low_i, close_i, volume_i) in data:
            if i >= npic:
                picture = QtGui.QPicture()
                p = QtGui.QPainter(picture)
                # 下跌绿色（实心）, 上涨红色（空心）
                if close_i < open_i:  # 阴线情况
                    p.setPen(pg.mkPen(QtGui.QColor(0, 255, 0), width=2))  # 设置画笔颜色，宽度
                    p.setBrush(pg.mkBrush(QtGui.QColor(0, 255, 0)))
                    p.drawRect(QtCore.QRectF(i - w, 0, w * 2, volume_i))  # 画矩形，实心成交量柱线

                elif close_i > open_i:  # 阳线情况
                    p.setPen(pg.mkPen(QtGui.QColor(255, 0, 0), width=2))  # red
                    p.setBrush(pg.mkBrush(QtGui.QColor(255, 0, 0)))  # red

                    # p.drawRect(QtCore.QRectF(i - w, open_i, w * 2, close_i - open_i)) # 如果画实心阳线，只需画个实心矩形即可
                    # 画空心阳线的时候，需要画四条线

                    p.drawLine(QtCore.QPointF(i - w, 0), QtCore.QPointF(i - w, volume_i))  # 画单根成交量柱线的左边线
                    p.drawLine(QtCore.QPointF(i + w, 0), QtCore.QPointF(i + w, volume_i))  # 画单根成交量柱线的右边线
                    p.drawLine(QtCore.QPointF(i - w, volume_i), QtCore.QPointF(i + w, volume_i))  # 画单根成交量柱线的下边线
                    p.drawLine(QtCore.QPointF(i - w, 0), QtCore.QPointF(i + w, 0))  # 画单根成交量柱线的上边线

                else:  # 平盘情况
                    p.setPen(pg.mkPen(QtGui.QColor(0, 0, 255), width=2))  # 十字线设为蓝色
                    p.setBrush(pg.mkBrush(QtGui.QColor(0, 0, 255)))  # 十字线设为蓝色

                    p.drawRect(QtCore.QRectF(i - w, 0, w * 2, volume_i))  # 画矩形，实心成交量柱线

                p.end()
                self.pictures.append(picture)


    def update(self):  # 手动重画
        if not self.scene() is None:
            self.scene().update()


    def paint(self, painter, opt, w):  # 自动重画
        rect = opt.exposedRect
        xmin, xmax = (max(0, int(rect.left())), min(int(len(self.pictures)), int(rect.right())))
        if not self.rect == (rect.left(), rect.right()) or self.picture is None:
            self.rect = (rect.left(), rect.right())
            self.picture = self.createPic(xmin, xmax)
            self.picture.play(painter)
        elif not self.picture is None:
            self.picture.play(painter)


    def createPic(self, xmin, xmax):  # 缓存图片
        picture = QtGui.QPicture()
        p = QtGui.QPainter(picture)
        [pic.play(p) for pic in self.pictures[xmin:xmax]]
        p.end()
        return picture


    def boundingRect(self):  # 定义边界
        return QtCore.QRectF(0, self.low, len(self.pictures), (self.high - self.low))


########################################################################
# 选择缩放功能支持
########################################################################
class CustomViewBox(pg.ViewBox):
    # ----------------------------------------------------------------------
    def __init__(self, *args, **kwds):
        pg.ViewBox.__init__(self, *args, **kwds)
        # 拖动放大模式
        # self.setMouseMode(self.RectMode)

    # 右键自适应
    # ----------------------------------------------------------------------
    def mouseClickEvent(self, ev):
        if ev.button() == QtCore.Qt.MouseButton.RightButton:
            self.autoRange()


class PlotWidget(KeyWrapper):
    def __init__(self, parent):
        super(PlotWidget, self).__init__(parent=parent)


########################################################################
class KLineWidget(KeyWrapper):
    """用于显示价格走势图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    clsId = 0  # 窗口标识

    # 保存K线数据的列表和Numpy Array对象
    listBar = []
    listVol = []
    listHigh = []
    listLow = []
    listSig = []
    listOpenInterest = []
    arrows = []

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, graph_type=GRAPH_TYPE_KBAR):
        """Constructor"""
        self.KLtitle = None
        self.pw = None
        self.grid = None
        self.lay_KL = None
        self.parent = parent
        super(KLineWidget, self).__init__(parent)

        self.index = None  # 当前序号   # 下标
        self.countK = 100  # 默认显示的Ｋ线根数

        KLineWidget.clsId += 1
        self.windowId = str(KLineWidget.clsId)

        # 画指标线需要的各种数据列表

        self.candle_bar_list = []
        self.mv1_list = []
        self.mv2_list = []

        # 缓存数据
        self.datas = pd.DataFrame()
        self.listSig = []
        self.listOpenInterest = []
        self.arrows = []

        self.indi_dic = {}  # 当前技术指标字典：key:指标名称，value：指标值序列

        # 所有K线上信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 所有副图上的信号图
        self.allSubColor = deque(['blue', 'green', 'yellow', 'white'])
        self.subSigData = {}
        self.subSigColor = {}
        self.subSigPlots = {}
        # 初始化完成
        self.initCompleted = False
        self.crosshair = None
        self.initUi()  # 调用函数

        self.popup_menu = None

        # 调用函数
        self.initUi()
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        pg.setConfigOptions(leftButtonPan=True, antialias=True)  # 禁止画框放大，并启用抗锯齿

        # 主图
        self.pw = pg.PlotWidget(background=QtGui.QColor(13, 9, 27))
        self.pw.setContentsMargins(0, 0, 0, 0)  # 去掉边框

        # 界面布局
        self.lay_KL = pg.GraphicsLayout(border=True)
        self.lay_KL.setContentsMargins(0, 0, 1, 1)  # 左、上、右、下的外边距，图形层与窗口边缘的距离
        self.lay_KL.setBorder(color=(255, 0, 0), width=1)  # 设置图层边框红色，粗1
        self.lay_KL.setZValue(0)
        self.lay_KL.setSpacing(3)  # 设置图层内图元间距，多个图元间距
        self.pw.setCentralItem(self.lay_KL)  # 设置主图

        # 初始化子图
        self.initplotKline()
        self.initplotVol()

        # 注册十字光标
        self.crosshair = Crosshair(self.pw, self)
        # 设置界面
        self.VboxL = QtWidgets.QVBoxLayout()  # 垂直布局
        self.VboxL.setContentsMargins(0, 0, 0, 0)  # Vboxlayout的外边距要去掉,这个非常难找
        self.VboxL.addWidget(self.pw)  # 添加主图
        self.setLayout(self.VboxL)  # 设置布局
        # 初始化完成
        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtWidgets.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        # 插入指标
        insert_indicator_act = QtWidgets.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

        # 副图数量
        second_graph_nums_act = QtWidgets.QAction('副图数量', self)
        second_graph_nums_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(second_graph_nums_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def makePI(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = self.lay_KL.addPlot(title=name) #pg.PlotItem(viewBox=vb, name=name,)# axisItems={'bottom': self.axisTime})
        plotItem._viewBox=vb
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def initplotVol(self):
        """初始化成交量子图"""
        self.pwVol = self.makePI('PlotVol')
        self.volume = CandlestickItem(self.listVol)
        self.pwVol.addItem(self.volume)
        self.pwVol.setMaximumHeight(150)
        self.pwVol.setXLink('PlotOI')
        # self.pwVol.hideAxis('bottom')

        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwVol)

    # ----------------------------------------------------------------------
    def initplotKline(self):
        """初始化蜡烛图子图"""
        self.pwKL = pg.PlotItem(name=('_'.join([self.windowId, 'PlotKL'])), axisItems=None)
        self.pwKL.setXLink('_'.join([self.windowId, 'PlotKL']))  # 设置x轴关联，使两个子图的x坐标一致
        self.pwKL.getViewBox().sigXRangeChanged.connect(self.set_pwKL_yRange)  # 子图的x轴范围改变信号
        self.pwKL.setMenuEnabled(False)  # 隐藏菜单
        self.pwKL.setClipToView(True)  # 在ViewBox可见范围内绘制所有数据
        self.pwKL.hideAxis('left')  # 隐藏左边坐标轴
        self.pwKL.showAxis('right')  # 显示右边坐标轴
        # self.pwKL.hideAxis('bottom')

        self.pwKL.setDownsampling(mode='peak')  # 缩减像素采样,峰值:通过画一个跟随原始数据的最小值和最大值的锯齿波向下采样。# 这种方法可以产生最好的数据可视化表示，但速度较慢。
        self.pwKL.setRange(xRange=(0, 1), yRange=(0, 1))  # 设置x、y轴范围
        self.pwKL.getAxis('right').setWidth(40)  # 设置右边坐标轴宽度
        self.pwKL.showGrid(True, True, alpha=0.3)  # 显示网格,alpha为网格的不透明度,范围0-1.0
        self.pwKL.setMaximumHeight(800)  # 图项最大高度
        self.pwKL.setMinimumHeight(400)  # 图项最小高度
        self.pwKL.hideButtons()  # 隐藏刻度按钮
        self.pwKL.setZValue(0)
        self.pwKL.getAxis('right').setStyle(tickFont=QFont("Arial", 8, QFont.Bold), autoExpandTextSpace=True)  # 设置右边坐标轴刻度字体
        self.pwKL.getAxis('bottom').setStyle(tickFont=QFont("Arial", 8, QFont.Bold), autoExpandTextSpace=True)  # 设置下边坐标轴刻度字体
        self.pwKL.getAxis('right').setPen(QtGui.QColor(255, 0, 0))  # y轴颜色
        self.pwKL.getAxis('bottom').setPen(QtGui.QColor(255, 0, 0))  # x轴颜色
        self.pwKL.getAxis('right').setTextPen(QtGui.QColor(150, 150, 150))  # y轴刻度颜色
        self.pwKL.getAxis('bottom').setTextPen(QtGui.QColor(150, 150, 150))  # x轴刻度颜色

        self.candle = CandlestickItem(self.datas)
        self.candle.setZValue(10)
        self.pwKL.addItem(self.candle)
        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwKL)

    # ----------------------------------------------------------------------
    def initplotOI(self):
        """初始化持仓量子图"""
        self.pwOI = self.makePI('PlotOI')
        self.curveOI = self.pwOI.plot()

    def initplotVol(self):
        """初始化成交量子图"""
        self.pwVol = pg.PlotItem(name=('_'.join([self.windowId, 'PlotVol'])), axisItems=None)
        self.pwVol.setXLink('_'.join([self.windowId, 'PlotKL']))  # 设置x轴关联，使两个子图的x坐标一致
        self.pwVol.getViewBox().sigXRangeChanged.connect(self.set_pwVol_yRange)  # 子图的x轴范围改变信号
        self.pwVol.setMenuEnabled(False)  # 隐藏菜单
        self.pwVol.setClipToView(True)  # 在ViewBox可见范围内绘制所有数据
        self.pwVol.hideAxis('left')  # 隐藏左边坐标轴
        self.pwVol.showAxis('right')  # 显示右边坐标轴
        self.pwVol.hideAxis('bottom')

        self.pwVol.setDownsampling(mode='peak')  # 缩减像素采样,峰值:通过画一个跟随原始数据的最小值和最大值的锯齿波向下采样。# 这种方法可以产生最好的数据可视化表示，但速度较慢。
        self.pwVol.setRange(xRange=(0, 1), yRange=(0, 1))  # 设置x、y轴范围
        self.pwVol.getAxis('right').setWidth(40)  # 设置右边坐标轴宽度
        self.pwVol.showGrid(True, True, alpha=0.3)  # 显示网格,alpha为网格的不透明度,范围0-1.0
        self.pwVol.setMinimumHeight(80)  # 图项最小高度
        self.pwVol.setMaximumHeight(130)  # 图项最大高度
        self.pwVol.hideButtons()  # 隐藏刻度按钮
        self.pwVol.setZValue(0)
        self.pwVol.getAxis('right').setStyle(tickFont=QFont("Arial", 8, QFont.Bold), autoExpandTextSpace=True)  # 设置右边坐标轴刻度字体
        self.pwVol.getAxis('bottom').setStyle(tickFont=QFont("Arial", 8, QFont.Bold), autoExpandTextSpace=True)  # 设置下边坐标轴刻度字体
        self.pwVol.getAxis('right').setPen(QtGui.QColor(255, 0, 0))  # y轴颜色
        self.pwVol.getAxis('bottom').setPen(QtGui.QColor(255, 0, 0))  # x轴颜色
        self.pwVol.getAxis('right').setTextPen(QtGui.QColor(150, 150, 150))  # y轴刻度颜色
        self.pwVol.getAxis('bottom').setTextPen(QtGui.QColor(150, 150, 150))  # x轴刻度颜色

        self.volume = VolumeItem(self.datas)
        self.pwVol.addItem(self.volume)
        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwVol)

        self.Curves_mv1 = self.pwVol.plot(self.mv1_list, pen=pg.mkPen(QtGui.QColor(255, 255, 0), width=2))
        self.Curves_mv2 = self.pwVol.plot(self.mv2_list, pen=pg.mkPen(QtGui.QColor(255, 255, 255), width=2))
        self.Curves_mv1.setZValue(15)
        self.Curves_mv2.setZValue(15)


    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.makePI('plot_indi')
        self.curve_ta = pg.PlotDataItem()
        self.pw_ta.addItem(self.curve_ta)
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plotVol(self, redraw=False, xmin=0, xmax=-1):
        """重画成交量子图"""
        if self.initCompleted:
            self.volume.generatePicture(self.listVol[xmin:xmax], redraw)  # 画成交量子图

    # ----------------------------------------------------------------------
    def plotKline(self, redraw=False, xmin=0, xmax=-1):
        """重画K线子图"""
        if self.initCompleted:
            self.candle.generatePicture(self.candle_bar_list[xmin:xmax], redraw)  # 画K线
            self.plotMark()  # 显示开平仓信号位置


    def plotVol(self, redraw=False, xmin=0, xmax=-1):
        """重画成交量子图"""
        if self.initCompleted:
            self.volume.generatePicture(self.candle_bar_list[xmin:xmax], redraw)  # 画成交量子图
            self.Curves_mv1.setData(self.mv1_list)
            self.Curves_mv2.setData(self.mv2_list)

    def plot_indi(self, redraw=False, xmin=0, xmax=-1):
        """重画技术指标子图"""
        if self.initCompleted:
            self.curve_ta.setData(y=self.listHigh[xmin:xmax] + [0], name="TA")  ##symbol='o',

            # self.pw_ta.plot()

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pwKL.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pwKL.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pwKL.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['low']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pwKL.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    #  界面刷新相关
    # ----------------------------------------------------------------------
    def refreshAll(self, redraw=True, update=False):
        """
        更新所有界面
        """
        # 调用画图函数
        self.index = len(self.datas)
        self.crosshair.datas = self.datas
        self.crosshair.signal.emit(None, None)
        # 设置横坐标

        self.axisTime = DatetimeAxisX(self.datas, orientation='bottom')  # 时间坐标轴
        self.pwKL.setAxisItems(axisItems={'bottom': self.axisTime})

        self.plotAll(redraw, 0, len(self.datas))
        if not update:
            self.updateAll()


    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        if xMax < 0:
            xMax = len(self.datas) - 1
        else:
            xMax = xMax

        self.pwKL.setLimits(xMin=xMin, xMax=xMax)
        self.pwVol.setLimits(xMin=xMin, xMax=xMax)
        self.pwMACD.setLimits(xMin=xMin, xMax=xMax)
        self.plotKline(redraw, xMin, xMax)  # K线图
        self.plotVol(redraw, xMin, xMax)  # K线副图，成交量
        self.PlotMACD(redraw, xMin, xMax)  # K线副图，MACD
        self.refresh()


    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume.update()
        self.candle.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pwKL.getViewBox(), 'low', 'high')
        update(self.pwVol.getViewBox(), 'volume', 'volume')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_countK = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        # self.pwOI.setLimits(xMin=xMin, xMax=xMax)
        self.pwKL.setLimits(xMin=xMin, xMax=xMax)
        self.pwVol.setLimits(xMin=xMin, xMax=xMax)
        # self.pw_ta.setLimits(xMin=xMin, xMax=xMax)

        self.plotKline(redraw, xMin, xMax)  # K线图
        self.plotVol(redraw, xMin, xMax)  # K线副图，成交量
        # self.plot_indi(redraw, xMin, xMax)  # K线副图，技术指标
        # self.plotOI(0, len(self.data))  # K线副图，持仓量
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        datas = self.data
        minutes = int(self.visible_countK / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes
        # self.pwOI.setRange(xRange=(xmin, xmax))
        self.pwKL.setRange(xRange=(xmin, xmax))
        self.pwVol.setRange(xRange=(xmin, xmax))
        # self.pw_ta.setRange(xRange=(xmin, xmax))
    def set_pwKL_yRange(self):  # 设置pwKL的y轴显示范围,该函数由sigXRangeChanged信号驱动
        datas = self.datas
        view = self.pwKL.getViewBox()
        vRange = view.viewRange()
        if self.crosshair:
            x = int(self.crosshair.xAxis)
            y = int(self.crosshair.yAxis)
            self.crosshair.signal.emit(x, y)

        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        try:
            xmax = min(xmax, len(datas) - 1)
        except:
            xmax = xmax
        if len(datas) > 0 and xmax > xmin:
            ymin = min(datas[xmin:xmax]['low'])
            ymax = max(datas[xmin:xmax]['high'])
            if ymin and ymax:
                view.setRange(yRange=(ymin, ymax))
            else:
                pass
        else:
            view.setRange(yRange=(0, 1))


    def set_pwVol_yRange(self):  # 设置pwVol的y轴显示范围,该函数由sigXRangeChanged信号驱动
        datas = self.datas
        view = self.pwVol.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        try:
            xmax = min(xmax, len(datas) - 1)
        except:
            xmax = xmax
        if len(datas) > 0 and xmax > xmin:
            ymax = max(datas[xmin:xmax]['volume'])
            view.setRange(yRange=(ymax // 13, ymax))
        else:
            view.setRange(yRange=(0, 1))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_countK = min(len(self.data), int(self.visible_countK * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_countK / 2 - 2 if x > self.x_center + self.visible_countK / 2 - 2 else x
            y = self.data[x][2]
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_countK = max(3, int(self.visible_countK / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_countK / 2 - 2 if x > self.x_center + self.visible_countK / 2 - 2 else x
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            x = int(self.crosshair.xAxis) - 1
            y = self.data[x]['close']
            if x <= self.x_center - self.visible_countK / 2 + 2 and self.x_center > 1:
                self.x_center -= 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            x = int(self.crosshair.xAxis) + 1
            y = self.data[x]['close']
            if x >= self.x_center + int(self.visible_countK / 2) - 2:
                self.x_center += 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pwKL.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pwKL.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'low', 'high'))

        view = self.pwVol.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clearData(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def onBar(self, bar, nWindow=20):
        """
        新增K线数据,K线播放模式
        nWindow : 最大数据窗口
        """
        # 是否需要更新K线
        newBar = False if len(self.data) > 0 and bar.datetime == self.data[-1].datetime else True
        nrecords = len(self.data) if newBar else len(self.data) - 1
        bar.openInterest = np.random.randint(0,
                                             3) if bar.openInterest == np.inf or bar.openInterest == -np.inf else bar.openInterest
        recordVol = (nrecords, bar.volume, 0, 0, bar.volume) if bar.close < bar.open else (
            nrecords, 0, bar.volume, 0, bar.volume)
        if newBar and any(self.data):
            self.data.resize(nrecords + 1, refcheck=0)
            self.listBar.resize(nrecords + 1, refcheck=0)
            self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.data):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.data):
            self.data[-1] = (bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)
            self.listBar[-1] = (nrecords, bar.open, bar.close, bar.low, bar.high)
            self.listVol[-1] = recordVol
        else:
            self.data = np.rec.array(
                [(datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, bar.open, bar.close, bar.low, bar.high)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.data)
        self.axisTime.update_xdict({nrecords: bar.datetime})
        self.listLow.append(bar.low)
        self.listHigh.append(bar.high)
        self.listOpenInterest.append(bar.openInterest)
        xMax = nrecords + 1
        xMin = max(0, nrecords - nWindow)
        if not newBar:
            self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    # ----------------------------------------------------------------------
    def convert_dataframe_local(self, df=pd.DataFrame):
        """
        将pandas.DataFrame 数据格式转变为做图可用的
        datas : DataFrame 数据格式，cols :  open, close, low, high, volume, index: datetime, ''yyyyMddhhssmm
        """
        # 设置中心点时间
        self.x_center = 0

        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        print(df.head())
        df['time_int'] = np.array(range(len(df.index)))
        # self.data = df[['open', 'close', 'low', 'high', 'volume']].to_records()  # , 'openInterest'
        self.data = df[list(df.columns)].to_records(False)
        self.fields_name = list(df.columns)
        self.axisTime.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))
        self.axisTime.update_xdict(xdict)
        self.resignData(self.data)
        # 更新画图用到的数据
        self.listBar = df[['time_int', 'open', 'close', 'low', 'high']].to_records(False)

        self.listHigh = list(df['high'])
        self.listLow = list(df['low'])
        # self.listOpenInterest = list(datas['openInterest'])
        # 成交量颜色和涨跌同步，K线方向由涨跌决定
        datas0 = pd.DataFrame()
        datas0['open'] = df.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
        datas0['close'] = df.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
        datas0['low'] = datas0['open']
        datas0['high'] = datas0['close']
        datas0['time_int'] = np.array(range(len(df.index)))
        self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        # 调用画图函数
        self.plotAll(True, 0, len(self.data))


class GraphWidget(KeyWrapper):
    """用于显示各类图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, fields_name=None, graph_type=GRAPH_TYPE_LINE):
        """Constructor"""
        self.parent = parent
        super(GraphWidget, self).__init__(parent)
        self.main_graph_type = graph_type

        # 当前序号
        self.x_center = 0  # 下标中间
        self.visible_count = 60  # 显示的数据长度
        self.graph_count = 1  # 图的数量
        self.other_graph = []  # 主图外的其它图列表

        # 缓存数据
        self.data = []
        self.fields_name = fields_name  # data 的字段名称

        self.indi_dic = {}  # 当前技术指标字典：key:指标名称，value：指标值序列

        # 所有信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        self.popup_menu = None

        # 调用函数
        self.initUi()
        time.sleep(1)
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.initCompleted = False
        self.grid = QtWidgets.QGridLayout()
        self.setWindowTitle('')
        # 主图
        self.pg_main_plot_widget = pg.PlotWidget()
        # 界面布局
        self.main_layout = pg.GraphicsLayout(border=(100, 100, 100))
        self.main_layout.setContentsMargins(10, 10, 10, 10)
        self.main_layout.setSpacing(0)
        self.main_layout.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.main_layout.setZValue(0)

        self.main_title = self.main_layout.addLabel('')
        self.pg_main_plot_widget.setCentralItem(self.main_layout)
        # 设置横坐标
        xdict = {}
        self.axis_x = StringAxisX(xdict, orientation='bottom')

        # 初始化主图
        self.init_main_plot()
        # 注册十字光标
        self.crosshair = Crosshair(self.pg_main_plot_widget, self, sync_num=1)
        # 设置界面

        self.grid.addWidget(self.pg_main_plot_widget)
        self.setLayout(self.grid)
        # self.setLayout(self.vb)
        #
        self.init_popup_menu()

        # 初始化完成

        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtGui.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        #
        insert_indicator_act = QtGui.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def make_plot_item(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axis_x})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def init_main_plot(self):
        """初始化主图"""
        self.pw_main_item = self.make_plot_item('PlotMain')
        if self.main_graph_type != GRAPH_TYPE_KBAR:
            self.main_curve = self.pw_main_item.plot()  # CandlestickItem(self.data)
            self.pw_main_item.addItem(self.main_curve)
        # self.pw_main_item.hideAxis('bottom')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_main_item)

    # ----------------------------------------------------------------------
    def initplotOI(self):
        """初始化持仓量子图"""
        self.pwOI = self.make_plot_item('PlotOI')
        self.curveOI = self.pwOI.plot()

        # self.lay_KL.nextRow()
        # self.lay_KL.addItem(self.pwOI)

    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.make_plot_item('plot_indi')
        self.curve_ta = self.pw_ta.plot()
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plot_bar(self, redraw=False, xmin=0, xmax=-1):
        """重画柱状图"""
        if self.initCompleted:
            self.volume.generatePicture(self.listVol[xmin:xmax], redraw)

    # ----------------------------------------------------------------------
    def plot_main(self, redraw=False, xmin=0, xmax=-1):
        """重画折线图"""
        if self.initCompleted:
            # self.main_curve = pg.PlotDataItem()
            self.main_curve.setData(y=self.data[xmin:xmax]['close'])
            # self.main_curve.addItem(self.curveOI1)
            # self.main_curve.generatePicture(self.listBar[xmin:xmax], redraw)  # 画 线

    def add_line(self, data, field_name='', graph_area_no=0):
        """增加折线"""

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pw_main_item.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pw_main_item.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pw_main_item.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['low']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pw_main_item.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume.update()
        self.main_curve.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pw_main_item.getViewBox(), 'low', 'high')
        update(self.pwVol.getViewBox(), 'volume', 'volume')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_count = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        self.pw_main_item.setLimits(xMin=xMin, xMax=xMax)
        self.plot_main(redraw, xMin, xMax)
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        data = self.data
        minutes = int(self.visible_count / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes

        self.pw_main_item.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_count = min(len(self.data), int(self.visible_count * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_count / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
            y = self.data[x][2]
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_count = max(3, int(self.visible_count / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_count / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            x = int(self.crosshair.xAxis) - 1
            y = self.data[x]['close']
            if x <= self.x_center - self.visible_count / 2 + 2 and self.x_center > 1:
                self.x_center -= 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            x = int(self.crosshair.xAxis) + 1
            y = self.data[x]['close']
            if x >= self.x_center + int(self.visible_count / 2) - 2:
                self.x_center += 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pw_main_item.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotDataInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pw_main_item.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'low', 'high'))

        # view = self.pwVol.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clearData(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def onBar(self, bar, nWindow=20):
        """
        新增K线数据,K线播放模式
        nWindow : 最大数据窗口
        """
        # 是否需要更新K线
        newBar = False if len(self.data) > 0 and bar.datetime == self.data[-1].datetime else True
        nrecords = len(self.data) if newBar else len(self.data) - 1
        bar.openInterest = np.random.randint(0,
                                             3) if bar.openInterest == np.inf or bar.openInterest == -np.inf else bar.openInterest
        recordVol = (nrecords, bar.volume, 0, 0, bar.volume) if bar.close < bar.open else (
            nrecords, 0, bar.volume, 0, bar.volume)
        if newBar and any(self.data):
            self.data.resize(nrecords + 1, refcheck=0)
            # self.listBar.resize(nrecords + 1, refcheck=0)
            # self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.data):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.data):
            self.data[-1] = (bar.datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)
            self.listBar[-1] = (nrecords, bar.open, bar.close, bar.low, bar.high)
            self.listVol[-1] = recordVol
        else:
            self.data = np.rec.array(
                [(datetime, bar.open, bar.close, bar.low, bar.high, bar.volume, bar.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, bar.open, bar.close, bar.low, bar.high)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.data)
        self.axis_x.update_xdict({nrecords: bar.datetime})
        self.listLow.append(bar.low)
        self.listHigh.append(bar.high)
        self.listOpenInterest.append(bar.openInterest)
        xMax = nrecords + 1
        xMin = max(0, nrecords - nWindow)
        if not newBar:
            self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    # ----------------------------------------------------------------------
    def convert_dataframe_local(self, df):
        """
        将pandas.DataFrame数据格式转变为做图可用的
        datas : 数据格式，cols : datetime, open, close, low, high
        """
        # 设置中心点时间
        self.x_center = 0

        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        df['time_int'] = np.array(range(len(df.index)))
        self.data = df[list(df.columns)].to_records(False)  # , 'openInterest'
        self.fields_name = list(df.columns)
        # self.listBar = df[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        # print(type(self.data))
        self.axis_x.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))
        self.axis_x.update_xdict(xdict)
        self.resignData(self.data)

        # 调用画图函数
        self.plotAll(True, 0, len(self.data))


########################################################################
# 功能测试
########################################################################
import sys
# from tqsdk.api import TqApi
#
# api = TqApi(1)
# serial = api.get_tick_serial("SHFE.ni1905")
# def on_update():
#     print ("SHFE.cu1803 serial change? ", api.is_changing(serial))
# api.register_update_notify(on_update)

if __name__ == '__main__':
    app = QApplication(sys.argv)
#
#     path = 'E:/data/day/rb1101.txt'
#
#     # read Tick
    from datetime import datetime,timedelta
    import pandas as pd
    from sqlalchemy import create_engine
    # from data.data_server import get_ticks_df
    # from utils.resample import ticks2bars

    port = 5432
    host = '192.168.10.5'
    database='ctp'
    user='postgres'
    password='steward1!'
    df = pd.DataFrame()
    try:
        # 使用SQLAlchemy创建引擎
        engine = create_engine(
            f"postgresql+psycopg2://{user}:{password}"
            f"@{host}:{port}/{database}"
        )

    except Exception as e:
        print( "数据库错误", f"连接数据库失败: {str(e)}")
    sd=datetime.now()-timedelta(10)
    ed=datetime.now()

    instrument_id='v2509'
    table_name='tmpl'
    select_tick = f'''select "InstrumentID", "DateTime", coalesce("PreSettlementPrice",0.), coalesce("PreClosePrice",0.), coalesce("PreOpenInterest",0.), 
    	"UpperLimitPrice", "LowerLimitPrice","OpenPrice", 	"HighestPrice", "LowestPrice", 
    	 "Volume", "Turnover", "AveragePrice" , "OpenInterest", "LastPrice",
    	"BidPrice1", "BidVolume1", "AskPrice1", "AskVolume1"
    	from tick.{table_name} 
    	where "InstrumentID"={instrument_id} and "DateTime" between {sd} and {ed} 
    	order by "DateTime" desc limit 1000;
    	'''
    table_name = 'bar'
    select_bar=f"""
                    SELECT date_time as date, open, close, low, high, volume 
                    FROM bar.{table_name} 
                    WHERE instrument_id = '{instrument_id}'
                    ORDER BY date_time ASC
                    limit 100;
                """

    ticks = pd.read_sql(select_bar,engine)

    print(ticks.columns)

    print(ticks.head())
    ui = KLineWidget(data=ticks)
    # ui = TickWidget(data=df)
    ui.show()

    app.exec()
